﻿//#undef UNITY_OPTIMIZE_LIVE2D
/*
 * Copyright(c) Live2D Inc. All rights reserved.
 * 
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at http://live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */


using System;
using System.Runtime.InteropServices;
using CxExtension.System.Diagnostics;
using Live2D.Cubism.Core.Unmanaged;
using UnityEngine;


namespace Live2D.Cubism.Core
{
	/// <summary>
	/// Extension for Cubism related arrays.
	/// </summary>
	public static class ArrayExtensionMethods
	{
		#region Parameters

		/// <summary>
		/// Finds a <see cref="CubismParameter"/> by its ID.
		/// </summary>
		/// <param name="self">Container.</param>
		/// <param name="id">ID to match.</param>
		/// <returns>Parameter on success; <see langword="null"/> otherwise.</returns>
		public static CubismParameter FindById(this CubismParameter[] self, string id)
		{
			return (self != null)
				? Array.Find(self, i => i.name == id)
				: null;
		}


		/// <summary>
		/// Revives (and sorts) <see cref="CubismParameter"/>s.
		/// </summary>
		/// <param name="self">Container.</param>
		/// <param name="model">TaskableModel to unmanaged model.</param>
		internal static void Revive(this CubismParameter[] self, CubismUnmanagedModel model)
		{
			Array.Sort(self, (a, b) => a.UnmanagedIndex - b.UnmanagedIndex);


#if UNITY_OPTIMIZE_LIVE2D
			var length = self.Length;
			for (var i = 0; i < length; ++i)
#else
            for (var i = 0; i < self.Length; ++i)
#endif
			{
				self[i].Revive(model);
			}
		}

		/// <summary>
		/// Writes opacities to unmanaged model.
		/// </summary>
		/// <param name="self">Source buffer.</param>
		/// <param name="unmanagedModel"></param>
		internal static void WriteTo(this CubismParameter[] self, CubismUnmanagedModel unmanagedModel)
		{
			// Get address.
			var unmanagedParameters = unmanagedModel.Parameters;
			var values = unmanagedParameters.Values;


			// Push.
#if UNITY_OPTIMIZE_LIVE2D
			var length = self.Length;
			for (var i = 0; i < length; ++i)
			{
				var it = self[i];
				values[it.UnmanagedIndex] = it.Value;
			}
#else
			for (var i = 0; i < self.Length; ++i)
            {
                values[self[i].UnmanagedIndex] = self[i].Value;
            }
#endif
		}

		/// <summary>
		/// Writes opacities to unmanaged model.
		/// </summary>
		/// <param name="self">Source buffer.</param>
		/// <param name="unmanagedModel"></param>
		internal static void ReadFrom(this CubismParameter[] self, CubismUnmanagedModel unmanagedModel)
		{
			// Get address.
			var unmanagedParameters = unmanagedModel.Parameters;

#if UNITY_OPTIMIZE_LIVE2D
			unsafe
			{
				var values = unmanagedParameters.Values.Pointer;
				var length = self.Length;
				// Pull.
				for (var i = 0; i < length; ++i)
				{
					var it = self[i];
					it.Value = values[it.UnmanagedIndex];
				}
			}
#else
            var values = unmanagedParameters.Values;
			// Pull.
			for (var i = 0; i < self.Length; ++i)
            {
                self[i].Value = values[self[i].UnmanagedIndex];
            }
#endif


		}

		#endregion

		#region Parts

		/// <summary>
		/// Finds a <see cref="CubismPart"/> by its ID.
		/// </summary>
		/// <param name="self"><see langword="this"/>.</param>
		/// <param name="id">ID to match.</param>
		/// <returns>Part if found; <see langword="null"/> otherwise.</returns>
		public static CubismPart FindById(this CubismPart[] self, string id)
		{
			return (self != null)
				? Array.Find(self, i => i.name == id)
				: null;
		}


		/// <summary>
		/// Revives (and sorts) <see cref="CubismPart"/>s.
		/// </summary>
		/// <param name="self">Container.</param>
		/// <param name="model">TaskableModel to unmanaged model.</param>
		internal static void Revive(this CubismPart[] self, CubismUnmanagedModel model)
		{
			Array.Sort(self, (a, b) => a.UnmanagedIndex - b.UnmanagedIndex);


			for (var i = 0; i < self.Length; ++i)
			{
				self[i].Revive(model);
			}
		}

		/// <summary>
		/// Writes opacities to unmanaged model.
		/// </summary>
		/// <param name="self">Source buffer.</param>
		/// <param name="unmanagedModel"></param>
		internal static void WriteTo(this CubismPart[] self, CubismUnmanagedModel unmanagedModel)
		{
			// Get address.
			var unmanagedParts = unmanagedModel.Parts;
			var opacities = unmanagedParts.Opacities;


			// Push.
			for (var i = 0; i < self.Length; ++i)
			{
				opacities[self[i].UnmanagedIndex] = self[i].Opacity;
			}
		}

		#endregion

		#region Drawables

		/// <summary>
		/// Finds a <see cref="CubismParameter"/> by its ID.
		/// </summary>
		/// <param name="self"><see langword="this"/>.</param>
		/// <param name="id">ID to match.</param>
		/// <returns>Part if found; <see langword="null"/> otherwise.</returns>
		public static CubismDrawable FindById(this CubismDrawable[] self, string id)
		{
			return (self != null)
				? Array.Find(self, i => i.name == id)
				: null;
		}


		/// <summary>
		/// Revives (and sorts) <see cref="CubismDrawable"/>s.
		/// </summary>
		/// <param name="self">Container.</param>
		/// <param name="model">TaskableModel to unmanaged model.</param>
		internal static void Revive(this CubismDrawable[] self, CubismUnmanagedModel model)
		{
			Array.Sort(self, (a, b) => a.UnmanagedIndex - b.UnmanagedIndex);

#if UNITY_OPTIMIZE_LIVE2D
			var length = self.Length;
			for (var i = 0; i < length; ++i)
#else
            for (var i = 0; i < self.Length; ++i)
#endif
			{
				self[i].Revive(model);
			}
		}


		/// <summary>
		/// Reads new data from a model.
		/// </summary>
		/// <param name="self">Buffer to write to.</param>
		/// <param name="unmanagedModel">Unmanaged model to read from.</param>
		internal static unsafe void ReadFrom(this CubismDynamicDrawableData[] self, CubismUnmanagedModel unmanagedModel)
		{
			ProfilerF.BeginSample("ArrayExtensionMethods.ReadFrom", 9);
			// Get addresses.
#if UNITY_OPTIMIZE_LIVE2D
			var drawables = unmanagedModel.Drawables;
			var flags = drawables.DynamicFlags.Pointer;
			var opacities = drawables.Opacities.Pointer;
			var drawOrders = drawables.DrawOrders.Pointer;
			var renderOrders = drawables.RenderOrders.Pointer;
			var vertexPositions = drawables.VertexPositions;
#else
			var drawables = unmanagedModel.Drawables;
            var flags = drawables.DynamicFlags;
            var opacities = drawables.Opacities;
            var drawOrders = drawables.DrawOrders;
            var renderOrders = drawables.RenderOrders;
            var vertexPositions = drawables.VertexPositions;
#endif
			var selfLength = self.Length;
			ProfilerF.BeginSample("ArrayExtensionMethods.ReadFromForeach", 6);
			// Pull data.
			for (var i = 0; i < selfLength; ++i)
			{
				//ProfilerF.BeginSample("ArrayExtensionMethods.ReadFromForeachOther", 6);
				var data = self[i];


				data.Flags = flags[i];
				data.Opacity = opacities[i];
				data.DrawOrder = drawOrders[i];
				data.RenderOrder = renderOrders[i];


				// Read vertex positions only if necessary.
				if (!data.AreVertexPositionsDirty)
				{
					continue;
				}
				//ProfilerF.EndSample();
				//ProfilerF.BeginSample("ArrayExtensionMethods.ReadFromForeachVertexPositions", 6);
				// Copy vertex positions.
				fixed (Vector3* dataVertexPositions = data.VertexPositions)
				{
					var vtLength = data.VertexPositions.Length;
#if UNITY_OPTIMIZE_LIVE2D
					var pos = vertexPositions[i].Pointer;
#endif
					for (var v = 0; v < vtLength; ++v)
					{
#if !UNITY_OPTIMIZE_LIVE2D
						var pos = vertexPositions[i];
#endif
						var start = (v * 2);
						var posNew = dataVertexPositions[v];
						posNew.x = pos[start];
						posNew.y = pos[start + 1];
						dataVertexPositions[v] = posNew;
					}
				}
				//ProfilerF.EndSample();
			}
			ProfilerF.EndSample();


			// Clear dynamic flags.
			drawables.ResetDynamicFlags();
			ProfilerF.EndSample();
		}

		#endregion
	}
}
